/* usb2SerialWrs.c - USB Serial class driver using the WRS USB2 API */

/*
 * Copyright (c) 2010-2013 Wind River Systems, Inc.
 *
 * The right to copy, distribute, modify, or otherwise make use
 * of this software may be licensed only pursuant to the terms
 * of an applicable Wind River license agreement.
 */

/*
modification history
--------------------
01l,02jul13,wyy  Remove compiler warning (WIND00423315)
01k,06may13,s_z  Remove compiler warning (WIND00356717)
01j,16apr13,ghs  Update comments
01i,19feb13,ghs  Add select IO support (WIND00403162)
01h,16feb13,ghs  Fix semaphore locking issue during read (WIND00403011)
01g,21dec12,ghs  Fix performance issue(WIND00394550)
01f,13dec11,m_y  Modify according to code check result (WIND00319317)
01e,01aug11,ghs  Fix issue found during code review (WIND00250716)
01d,06jul11,ghs  Improve match function for vendor device and add support of
                 IAD Device (WIND00183500)
01c,02sep10,ghs  Use OS_THREAD_FAILURE to check taskSpawn failure (WIND00229830)
01b,06jul10,m_y  Set all the resources to NULL after release,
                 Add valid check after allocate resource (WIND00183499)
01a,10jun10,ghs  written
*/

/*
DESCRIPTION

This module is Wind River System USB CDC Serial emulator adapter driver for
USB-to-Serial driver based on WRS USB 2.x API. This module works as a general
higher layer on hardware configure layer.

The WRS CDC USB-to-Serial adapter driver supply IOS interface to USB-to-Serial
class driver by register its vendorID.

For WRS CDC device, in urb is needed every 16 ms. For a higher performance,
this module generate urb without delay.

A input task will be created for poll-input data from device when device is
attached. If pc console is configured, this task emulate "interrupt" mode for
pc console when input data arrived.

INITIALIZATION

This adapter driver must be registered to USB-to-Serial class driver before
class driver initialization. Use usb2SerialWrsInit() to finish this work.

OTHER FUNCTIONS

This driver also support ioctl interface. The following ioctl codes
are supported:

\ml

\m 1. SIO_BAUD_SET (0x1003)
      set baudrate of device

\m 2. SIO_BAUD_GET (0x1004)
      get baudrate of device

\m 3. FIONREAD (0x1)
      get num chars available to read.

\m 4. FIOSELECT (0x1C)
      register the task for waking up when data is available.

\m 5. FIOUNSELECT (0x1D)
      deregister the task for waking up when data is available.
\me

INCLUDE FILES: vxWorks.h usb2/usb2Serial.h

*/

/* includes */

#include <vxWorks.h>
#include <usb2/usb2Serial.h>

/* defines */

#define USB2_SER_WRS_CDC_CTRL_INTF          0
#define USB2_SER_WRS_CDC_DATA_INTF          1

#define USB2_SER_WRS_CTRL_INTF_CLASS        0x02
#define USB2_SER_WRS_CTRL_INTF_SUB_CLASS    0x02

#define USB2_SER_WRS_DATA_INTF_CLASS        0x0A
#define USB2_SER_WRS_DATA_INTF_SUB_CLASS    0x00

/* #define USB2_SER_WRS_CDC_IN_BUFFER_SIZE     512 wming change */
/* #define USB2_SER_WRS_CDC_OUT_BUFFER_SIZE    512 wming change */

#define USB2_SER_WRS_CDC_IN_BUFFER_SIZE     (512)
#define USB2_SER_WRS_CDC_OUT_BUFFER_SIZE    (512)

#define WRSCDC_WAIT_FOR_DEL                 16

#define WRS_CDC_DEVICE_ID_COUNT             4

/* adapter name register to USB-to-Serial driver */

#define USB2_SER_WRS_DRIVER_TEAM_NAME       "usbWrsCDCSer"

#define USB2_SER_WRS_DEVICE_INFO(uVid, uPid, uClass, uSubClass) \
{ \
    {NULL}, USBD_MATCH_NOTIFY_AS_INTERFACE, \
    uVid, uPid, 0, 0, 0, 0, 0, 0, \
    uClass, uSubClass, 0, \
    USBD_MATCH_ID_VENDORID | USBD_MATCH_ID_PRODUCTID \
    | USBD_MATCH_ID_INTERFACE_CLASS | USBD_MATCH_ID_INTERFACE_SUBCLASS, \
    0 \
}

/* imports */

IMPORT INT32    usb2SerialDefaultBaudRate;

IMPORT INT32    usb2SerialCommonTaskPriority;

IMPORT INT32    usb2SerialTimeOut;

/* IMPORT UINT32   usb2SerWrsPollInterval; wming change */
UINT32   usb2SerWrsPollInterval = 0;

#if ((_WRS_VXWORKS_MAJOR == 6) && (_WRS_VXWORKS_MINOR >= 9)) || \
    (_WRS_VXWORKS_MAJOR > 6)
IMPORT UINT32   usb2SerDebug;
#endif

/* locals */

/* forward declarations */

LOCAL STATUS    usb2SerialWrsOpen(USB2_SERIAL_DEVICE *, INT32);

LOCAL ssize_t   usb2SerialWrsRead(USB2_SERIAL_DEVICE *, char *, int);

LOCAL ssize_t   usb2SerialWrsWrite(USB2_SERIAL_DEVICE *, char *, int);

LOCAL STATUS    usb2SerialWrsClose(USB2_SERIAL_DEVICE *);

LOCAL int       usb2SerialWrsIoctl(USB2_SERIAL_DEVICE *, INT32, VOID *);

LOCAL USBHST_STATUS usb2SerialWrsParseConfig(USB2_SERIAL_ADAPTER_INFO *,
                                             UINT32, UINT8,
                                             USB2_SERIAL_DEVICE_CONFIG_INFO *);

LOCAL VOID usb2SerialWrsCreate(USB2_SERIAL_DEVICE *);

LOCAL VOID usb2SerialWrsRemove(USB2_SERIAL_DEVICE *);

LOCAL VOID usb2SerialWrsCommonTask(USB2_SERIAL_DEVICE *, OS_EVENT_ID);

/* adapter information register to USB-to-Serial driver */

LOCAL USB2_SERIAL_DEVICE_ID usb2SerialWrsDevIdList[WRS_CDC_DEVICE_ID_COUNT] =
    {
    USB2_SER_WRS_DEVICE_INFO(0x5719, 0x3399, 0x02, 0x02),
    USB2_SER_WRS_DEVICE_INFO(0x1439, 0xFF01, 0x02, 0x02),
    USB2_SER_WRS_DEVICE_INFO(0x1439, 0xFF02, 0x02, 0x02),
    USB2_SER_WRS_DEVICE_INFO(0x1439, 0xFF03, 0x02, 0x02)
    };

LOCAL USB2_SERIAL_ADAPTER_INFO usb2SerialWrsDriver =
    {
    (INT8*)"Wind River Systems USB CDC Serial emulator",
    USB2_SER_WRS_CDC_IN_BUFFER_SIZE,
    USB2_SER_WRS_CDC_OUT_BUFFER_SIZE,
        {
        usb2SerialWrsOpen,
        usb2SerialWrsRead,
        usb2SerialWrsWrite,
        usb2SerialWrsClose,
        usb2SerialWrsIoctl,
        usb2SerialWrsParseConfig,
        usb2SerialWrsCreate,
        usb2SerialWrsRemove
        }
    };

/*******************************************************************************
*
* usb2SerialWrsCreate - callback function for usb serial driver when device add
*
* This routine is callback function for usb serial driver when device is added.
* WRS CDC can only reply a IN urb after current urb timeout, so change the
* timeout value to a smaller value can get data more quickly.
*
* This routine will create common task for polling input.
*
* RETURNS: N/A
*
* ERRNO: N/A
*
* \NOMANUAL
*/

LOCAL VOID usb2SerialWrsCreate
    (
    USB2_SERIAL_DEVICE *    pDevice
    )
    {
    OS_EVENT_ID     eventId = OS_INVALID_EVENT_ID;
    char            taskName[16] = {"t"};

    USB2_SER_VDBG("usb2SerialWrsCreate(): Start\n", 1, 2, 3, 4, 5, 6);

    if (pDevice == NULL)
        {
        USB2_SER_ERR("usb2SerialWrsCreate(): Parameter error\n",
                     1, 2, 3, 4, 5, 6);

        return;
        }

    /*
     * NOTE:
     *   here we must change the time out value because WRS CDC device may not
     * reply the IN urb very fast, so need change it to smaller value to
     * generate IN urb faster.
     */

    pDevice->deviceTimeout = WRSCDC_WAIT_FOR_DEL;

    /* create a event for input task, wait for the task start up */

    if ((eventId = OS_CREATE_EVENT(OS_EVENT_NON_SIGNALED))
            == OS_INVALID_EVENT_ID)
        {
        USB2_SER_ERR("usb2SerialWrsCreate(): Create event fail\n",
                     1, 2, 3, 4, 5, 6);

        return;
        }

    /* init task name */

    memcpy(taskName + 1, pDevice->pUsb2ClassDevice->pDeviceName + 1, 14);

    if (taskName[1] >= 'a' && taskName[1] <= 'z')
        taskName[1] = (char)(taskName[1] - 32);

    /* create input task */

    pDevice->inTaskId = taskSpawn(taskName, usb2SerialCommonTaskPriority,
                                  OS_THREAD_OPTIONS, OS_THREAD_STACK_SIZE,
                                  (FUNCPTR) usb2SerialWrsCommonTask,
                                  (long)pDevice,
                                  (long)eventId, 0, 0, 0, 0, 0, 0, 0, 0);

    if (pDevice->inTaskId == OS_THREAD_FAILURE)
        {
        USB2_SER_ERR("usb2SerialWrsCreate(): Can not create common task\n",
                     1, 2, 3, 4, 5, 6);

        OS_DESTROY_EVENT(eventId);

        return;
        }

    /* wait for the input task startup successfully */

    if (OS_WAIT_FOR_EVENT(eventId,
            OS_CONVERT_MILLISECONDS_TO_WAIT_VALUE(usb2SerialTimeOut))
        == ERROR)
        {
        USB2_SER_ERR("usb2SerialWrsCreate(): Wait event time out\n",
                     1, 2, 3, 4, 5, 6);

        taskDelete(pDevice->inTaskId);
        pDevice->inTaskId = OS_THREAD_FAILURE;

        OS_DESTROY_EVENT(eventId);

        return;
        }

    OS_DESTROY_EVENT(eventId);

    return;
    }

/*******************************************************************************
*
* usb2SerialWrsRemove - callback function when device remove
*
* This routine is callback function for usb serial driver when device is
* removing. It will send a event to common task to delete it.
*
* RETURNS: N/A
*
* ERRNO: N/A
*
* \NOMANUAL
*/

LOCAL VOID usb2SerialWrsRemove
    (
    USB2_SERIAL_DEVICE *    pDevice
    )
    {
    USB2_SER_VDBG("usb2SerialWrsRemove(): Start\n", 1, 2, 3, 4, 5, 6);

    if (pDevice == NULL)
        {
        USB2_SER_ERR("usb2SerialWrsRemove(): Parameter error\n",
                     1, 2, 3, 4, 5, 6);

        return;
        }

    /* must get the event first, or it may occur error */

    if (semTake(pDevice->inDataMutex, WAIT_FOREVER) == ERROR)
        {
        USB2_SER_ERR("usb2SerialWrsRemove(): Time out\n", 1, 2, 3, 4, 5, 6);

        return;
        }

    USB2_SER_DBG("usb2SerialWrsRemove(): Send close signal\n",
                 1, 2, 3, 4, 5, 6);

    /* wait for common task exit */

    while (pDevice->inTaskId != OS_THREAD_FAILURE)
        {
        /* set terminate event to common task */

        semGive(pDevice->terminateEvent);

        /* clear buffer */

        pDevice->inDataSize = 0;

        rngFlush(pDevice->hInRingBuffer);

        /* flush all pending read requests */

        semFlush(pDevice->inDataEvent);

        /* release mutex for other tasks run */

        semGive(pDevice->inDataMutex);

        OS_DELAY_MS(WRSCDC_WAIT_FOR_DEL);
        }

    return;
    }

/*******************************************************************************
*
* usb2SerialWrsCommonTask - input task for device
*
* This routine is task function for WRS USB CDC device input. WRS USB CDC device
* need to generate a IN urb every 16 ms. This routine is used for this.
*
* This task is created when device open and destroy by device close
*
* If this device is attached as a SIO device, when input data arrived, the data
* will be sent to tty lib in this task.
*
* RETURNS: N/A
*
* ERRNO: N/A
*
* \NOMANUAL
*/

LOCAL VOID usb2SerialWrsCommonTask
    (
    USB2_SERIAL_DEVICE *    pDevice,
    OS_EVENT_ID             eventId
    )
    {
    UINT32  actLen = (UINT32)USB2_SER_WRS_CDC_IN_BUFFER_SIZE;
    STATUS  ret;
    int     i;

    USB2_SER_VDBG("usb2SerialWrsCommonTask(): Start\n", 1, 2, 3, 4, 5, 6);

    if (NULL == pDevice
        || NULL == pDevice->inDataBuffer
        || NULL == pDevice->inDataEvent
        || NULL == pDevice->inDataMutex
        || NULL == eventId)
        {
        USB2_SER_ERR("usb2SerialWrsCommonTask(): Parameter error\n",
                     1, 2, 3, 4, 5, 6);

        return;
        }

    OS_RELEASE_EVENT (eventId);

    /*
     * This task runs when a USB-to-Serial adapter device attached,
     * generates an IN URB as fast as it can, uses the IN URB packet to get
     * device input data.
     */

    while (TRUE)
        {
        if (OS_WAIT_FOR_EVENT(pDevice->terminateEvent, NO_WAIT) == OK)
            break;

        actLen = (UINT32)USB2_SER_WRS_CDC_IN_BUFFER_SIZE;

        memset(pDevice->inDataBuffer, 0, USB2_SER_WRS_CDC_IN_BUFFER_SIZE);

        ret = usb2SerialBulkTransfer(pDevice,
                                     pDevice->pInUrb,
                                     pDevice->bInEndPointAddr,
                                     &actLen,
                                     (UINT8 *)pDevice->inDataBuffer);

        /* if get data, put them to ring buffer */

        if (actLen > 0 && ret == OK)
            {
            USB2_SER_VDBG("usb2SerialWrsCommonTask(): Get input data "
                          "size [%d] \ncontext [%s] \nstatus[%x]\n",
                          actLen , pDevice->inDataBuffer,
                          pDevice->uStatusReg, 4, 5, 6);

            /*
             * If this device attach to console or SIO device, send data to
             * tty lib.
             *
             * Here we do NOT do any protect, because SIO device will not be
             * remove or change after initialization. So pUsbPcConSioChan
             * pointer is always correct.
             */

            if (pDevice->pUsbPcConSioChan != NULL
                && pDevice->pUsbPcConSioChan->channelMode == SIO_MODE_INT)
                {
                /* get data */

                for (i = 0; i < actLen; i++)
                    {
                    /* call tty lib callback */

                    (*pDevice->pUsbPcConSioChan->putRcvChar)
                        (pDevice->pUsbPcConSioChan->putRcvArg,
                         pDevice->inDataBuffer[i]);
                    }
                }
            else
                {
                /*
                 * no SIO device attached or work in polling mode, put them
                 * into ring buffer.
                 */

                if (semTake(pDevice->inDataMutex,
                        OS_CONVERT_MILLISECONDS_TO_WAIT_VALUE(pDevice->deviceTimeout))
                    == ERROR)
                    {
                    USB2_SER_WARN("usb2SerialWrsCommonTask(): time out\n",
                                  1, 2, 3, 4, 5, 6);

                    continue;
                    }

                /* re-check if need to exit common task */

                if (OS_WAIT_FOR_EVENT(pDevice->terminateEvent, NO_WAIT) == OK)
                    {
                    semGive(pDevice->inDataMutex);

                    break;
                    }

                for (i = 0; i < actLen; i += pDevice->wMaxPacketSizeIn)
                    {
                    rngBufPut(pDevice->hInRingBuffer,
                              pDevice->inDataBuffer + i,
                              actLen - i >= pDevice->wMaxPacketSizeIn ?
                                  pDevice->wMaxPacketSizeIn
                                  : actLen - i);
                    }

                /* updata inDataSize value */

                pDevice->inDataSize = rngNBytes(pDevice->hInRingBuffer);

                /* release a event, allow other task can operate in buffer */

                OS_RELEASE_EVENT(pDevice->inDataEvent);

                semGive(pDevice->inDataMutex);

                if (pDevice->pOwner)
                    {
                     if (0 < selWakeupListLen(pDevice->pSelWakeupList))
                        {
                        selWakeupAll (pDevice->pSelWakeupList, SELREAD);
                        }
                    }
                }

            /* if ring buffer not empty, update bufNotEmpty flag */

            if (pDevice->inDataSize > 0
                && pDevice->pUsbPcConSioChan != NULL)
                pDevice->pUsbPcConSioChan->bufNotEmpty = TRUE;
            }


        /*
         * NOTE:
         *   By default, here we only give a taskDelay(0), because we can not do
         * a delay less than 1 tick. taskDelay(0) can get better read
         * performance when baudrate is very high.
         *
         *   If it cost too much CPU time here, change parameter
         * USB_GEN2_SER_WRSCDC_POLL_INTERVAL.
         */

        if (usb2SerWrsPollInterval == 0)
            taskDelay(0);
        else
            OS_DELAY_MS(usb2SerWrsPollInterval);
        }

    /* flush all pend read request */

    semFlush(pDevice->inDataEvent);

    /* set it to invalid, prevent other read requests */

    pDevice->inTaskId = OS_THREAD_FAILURE;

    USB2_SER_DBG("usb2SerialWrsCommonTask(): End\n", 1, 2, 3, 4, 5, 6);

    return ;
    }

/*******************************************************************************
*
* usb2SerialWrsOpen - open WRS USB CDC device
*
* This routine is used for open a WRS USB CDC device. Before the device can be
* read or write, it need to be reset port and set a valid baud rate.
*
* For input data, this routine create a task for input.
*
* RETURNS: OK or ERROR if some error occurs
*
* ERRNO: N/A
*
* \NOMANUAL
*/

LOCAL STATUS usb2SerialWrsOpen
    (
    USB2_SERIAL_DEVICE *    pDevice,
    INT32                   mode
    )
    {
    USB2_SER_VDBG("usb2SerialWrsOpen(): Start\n", 1, 2, 3, 4, 5, 6);

    if (pDevice == NULL)
        {
        USB2_SER_ERR("usb2SerialWrsOpen(): Parameter error\n",
                     1, 2, 3, 4, 5, 6);

        return ERROR;
        }

    USB2_SER_DBG("usb2SerialWrsOpen(): pDevice %p name %s mode 0x%08X\n",
                 pDevice, pDevice->pUsb2ClassDevice->pDeviceName, mode,
                 4, 5, 6);

    pDevice->baudRate = usb2SerialDefaultBaudRate;

    /* flush input buffer */

    rngFlush(pDevice->hInRingBuffer);

    pDevice->inDataSize = 0;

    return OK;
    }

/*******************************************************************************
*
* usb2SerialWrsRead - read data from a WRS USB CDC device
*
* This routine is used for read data from a WRS USB CDC device.
*
* Read data is block operation wait for data arrived. This routine wait for
* input task event and try to get data from in buffer.
*
* RETURNS: actual read data size or ERROR
*
* ERRNO: N/A
*
* \NOMANUAL
*/

LOCAL ssize_t usb2SerialWrsRead
    (
    USB2_SERIAL_DEVICE *    pDevice,
    char *                  pBuffer,
    int                     bfrSize
    )
    {
    ssize_t actLen;

    USB2_SER_VDBG("usb2SerialWrsRead(): Start\n", 1, 2, 3, 4, 5, 6);

    USB2_SER_DBG("usb2SerialWrsRead(): pDevice %p buffer %p size %d\n",
                 pDevice, pBuffer, bfrSize, 4, 5, 6);

    if (pBuffer == NULL || pBuffer == NULL || bfrSize <= 0
        || pDevice->inTaskId == OS_THREAD_FAILURE)
        {
        USB2_SER_ERR("usb2SerialWrsRead(): Parameter error\n",
                     1, 2, 3, 4, 5, 6);

        return ERROR;
        }

    /* wait for the data arrive event */

    if (OS_WAIT_FOR_EVENT(pDevice->inDataEvent,
            OS_CONVERT_MILLISECONDS_TO_WAIT_VALUE(pDevice->deviceTimeout))
        == ERROR)
        {
        USB2_SER_WARN("usb2SerialWrsRead(): Time out\n",
                      1, 2, 3, 4, 5, 6);

        return 0;
        }

    if (semTake(pDevice->inDataMutex,
            OS_CONVERT_MILLISECONDS_TO_WAIT_VALUE(pDevice->deviceTimeout))
        == ERROR)
        {
        USB2_SER_WARN("usb2SerialWrsRead(): Time out\n",
                      1, 2, 3, 4, 5, 6);

        return 0;
        }

    pDevice->inDataSize = rngNBytes(pDevice->hInRingBuffer);

    /* get data actual length */

    actLen = ((bfrSize < pDevice->inDataSize) ? bfrSize : pDevice->inDataSize);

    /* copy data from ring buffer to user buffer */

    rngBufGet(pDevice->hInRingBuffer, pBuffer, actLen);

    /* recalculate data size */

    pDevice->inDataSize = rngNBytes(pDevice->hInRingBuffer);

    if (pDevice->inDataSize > 0)
        OS_RELEASE_EVENT(pDevice->inDataEvent);

    semGive(pDevice->inDataMutex);

    if (pDevice->pUsbPcConSioChan != NULL)
        {
        /* update the flag */

        if (pDevice->inDataSize > 0)
            pDevice->pUsbPcConSioChan->bufNotEmpty = TRUE;
        else
            pDevice->pUsbPcConSioChan->bufNotEmpty = FALSE;
        }

    USB2_SER_VDBG("usb2SerialWrsRead(): Read complete context %s size %d\n",
                  pBuffer, actLen, 3, 4, 5, 6);

    return (ssize_t)actLen;
    }

/*******************************************************************************
*
* usb2SerialWrsWrite - write data to a WRS USB CDC device
*
* This routine is used for write data to a WRS USB CDC device. Write data is
* block operation wait for data write complete.
*
* RETURNS: actual write data size or ERROR
*
* ERRNO: N/A
*
* \NOMANUAL
*/

LOCAL  ssize_t usb2SerialWrsWrite
    (
    USB2_SERIAL_DEVICE *    pDevice,
    char *                  pBuffer,
    int                     bfrSize
    )
    {
    UINT32  actLen;
    UINT32  count;
    UINT32  transferSize;
    STATUS  ret = ERROR;
    char *  pUrbBuffer;
#ifdef WRS_WRITE_ERROR_CONTINUE
    STATUS  tmpret = OK;
    UINT32  lostLen = 0;
#endif

    USB2_SER_VDBG("usb2SerialWrsWrite(): Start\n", 1, 2, 3, 4, 5, 6);

    if (pDevice == NULL || pBuffer == NULL || bfrSize < 0
        || pDevice->inTaskId == OS_THREAD_FAILURE)
        {
        USB2_SER_ERR("usb2SerialWrsWrite(): Parameter error\n",
                     1, 2, 3, 4, 5, 6);

        return ERROR;
        }

    /* zero size write should be ignored and return OK */

    if (bfrSize == 0)
        {
        USB2_SER_VDBG("usb2SerialWrsWrite(): zero length packet, ignore\n",
                      1, 2, 3, 4, 5, 6);

        return OK;
        }

    USB2_SER_VDBG("usb2SerialWrsWrite(): pDevice %p buffer %p size %d\n",
                 pDevice, pBuffer, bfrSize, 4, 5, 6);

    USB2_SER_VDBG("usb2SerialWrsWrite(): Context [%s]\n",
                 pBuffer, 2, 3, 4, 5, 6);

    /* alloc memory and copy data from user buffer to this buffer */

    pUrbBuffer = OS_MALLOC(bfrSize);

    if (NULL == pUrbBuffer)
        {
        USB2_SER_ERR("usb2SerialWrsWrite(): Malloc buffer error\n",
                     1, 2, 3, 4, 5, 6);
        return ERROR;
        }

    OS_MEMCPY(pUrbBuffer, pBuffer, bfrSize);

    USB2_SER_VDBG("usb2SerialWrsWrite(): pDevice %p buffer %p context %s "
                  "size %d\n", pDevice ,pBuffer, pBuffer, bfrSize, 5, 6);

    /* send out the data */

    count = 0;

    while(count < bfrSize)
        {
        /* calculate the transfer size, do not larger than max out buffer */

        if (bfrSize - count < USB2_SER_WRS_CDC_OUT_BUFFER_SIZE)
            transferSize = bfrSize - count;
        else
            transferSize = USB2_SER_WRS_CDC_OUT_BUFFER_SIZE;

        actLen = transferSize;

        ret = usb2SerialBulkTransfer(pDevice,
                                     pDevice->pOutUrb,
                                     pDevice->bOutEndPointAddr,
                                     &actLen,
                                     (UINT8 *)pUrbBuffer + count);

        count = (UINT32)(count + transferSize);

#ifndef WRS_WRITE_ERROR_CONTINUE
        if (ret != OK || actLen < transferSize)
            {
            USB2_SER_ERR("usb2SerialWrsWrite(): Failed write, reset port\n",
                         1, 2, 3, 4, 5, 6);

            break;
            }
#else
        /*
         * NOTE:
         *   WRS_WRITE_ERROR_CONTINUE is used for continue transfer when a long
         * transfer is partly error.
         */

        if (ret != OK)
            {
            USB2_SER_WARN("usb2SerialWrsWrite(): Failed write partly\n",
                          1, 2, 3, 4, 5, 6);

            /* because there is only one return value, save one error is OK */

            tmpret = ret;
            }

        if (actLen < transferSize)
            {
            lostLen = (UINT32)(transferSize - actLen);

            USB2_SER_WARN("usb2SerialWrsWrite(): Lost write some data\n",
                          1, 2, 3, 4, 5, 6);
            }
#endif
        }

#ifdef WRS_WRITE_ERROR_CONTINUE
    count = (UINT32)(count - lostLen);
#endif

    /* if send URB returned, this buffer is not useful any more */

    OS_FREE(pUrbBuffer);

    USB2_SER_VDBG("usb2SerialWrsWrite(): Write complete, size %d\n",
                  count, 2, 3, 4, 5, 6);

    if (ret != OK)
        return (ssize_t)ret;
    else
        return (ssize_t)count;
    }

/*******************************************************************************
*
* usb2SerialWrsIoctl - IO control function for WRS USB CDC device
*
* This routine is used for IO control function. This routine do not send any usb
* request, because WRS USB CDC is not a real usb serial device.
*
* The following ioctl codes are supported:
*
* \ml
*
* \m 1. SIO_BAUD_SET (0x1003)
*       set baudrate of device
*
* \m 2. SIO_BAUD_GET (0x1004)
*       get baudrate of device
*
* \m 3. FIONREAD (0x1)
*       get num chars available to read.
*
* \m 4. FIOSELECT (0x1C)
*       register the task for waking up when data is available.
*
* \m 5. FIOUNSELECT (0x1D)
*       deregister the task for waking up when data is available.
* \me
*
* RETURNS: OK or error number if some error occurs
*
* ERRNO: N/A
*
* \NOMANUAL
*/

LOCAL int usb2SerialWrsIoctl
    (
    USB2_SERIAL_DEVICE *    pDevice,
    INT32                   function,
    VOID *                  arg
    )
    {
    int ret = EIO;
    UINT16  actLen = 0;

    USB2_SER_VDBG("usb2SerialWrsIoctl(): Start\n", 1, 2, 3, 4, 5, 6);

    if (pDevice == NULL)
        {
        USB2_SER_ERR("usb2SerialWrsIoctl(): Parameter error\n",
                     1, 2, 3, 4, 5, 6);

        return ERROR;
        }

    USB2_SER_VDBG("usb2SerialWrsIoctl(): pDevice %p function %d arg %p\n",
                  (ULONG)pDevice, function, (ULONG)arg, 4, 5, 6);

    switch(function)
        {
        /* baud rate operation only change device data structrue value */
        case SIO_BAUD_SET:
            if (OK != usb2TakeDevice(pDevice->pUsb2ClassDevice))
                {
                USB2_SER_ERR("usb2SerialWrsIoctl(): Cannot take device "
                             "ownership\n", 1, 2, 3, 4, 5, 6);

                return ERROR;
                }

            ret = usb2SerialSubmitSetupUrb(pDevice,
                                    (INT8)0x21,
                                    (INT8)0x20,
                                    0,
                                    0,
                                    &actLen,
                                    NULL);    

            logMsg("Run here! ret = %d\n",ret,2,3,4,5,6);
            pDevice->baudRate = (UINT32)(ULONG)arg;

            ret = OK;

            usb2ReleaseDevice(pDevice->pUsb2ClassDevice);

            break;
        case SIO_BAUD_GET:
            if (OK != usb2TakeDevice(pDevice->pUsb2ClassDevice))
                {
                USB2_SER_ERR("usb2SerialWrsIoctl(): Cannot take device "
                             "ownership\n", 1, 2, 3, 4, 5, 6);

                return ERROR;
                }

            *(INT32 *)arg = pDevice->baudRate;

            usb2ReleaseDevice(pDevice->pUsb2ClassDevice);

            ret = OK;

            break;

        case FIONREAD:

            *(size_t *)arg = rngNBytes(pDevice->hInRingBuffer);

            ret = OK;

            break;
        case FIOSELECT:

            selNodeAdd(pDevice->pSelWakeupList, (SEL_WAKEUP_NODE *)arg);

            if (rngNBytes(pDevice->hInRingBuffer) > 0)
                {
                selWakeup((SEL_WAKEUP_NODE *) arg);
                }

            ret = OK;

            break;
        case FIOUNSELECT:

            selNodeDelete(pDevice->pSelWakeupList, (SEL_WAKEUP_NODE *)arg);

            ret = OK;
            break;

        case SIO_HW_OPTS_SET:
        case SIO_HW_OPTS_GET:
        case SIO_OPEN:
        default:
            ret = ENOSYS;
        }

    return ret;
    }

/*******************************************************************************
*
* usb2SerialWrsClose - close WRS USB CDC device
*
* This routine is used to close a WRS USB CDC device. It do nothing currently.
*
* RETURNS: OK or ERROR if some error occurs
*
* ERRNO: N/A
*
* \NOMANUAL
*/

LOCAL STATUS usb2SerialWrsClose
    (
    USB2_SERIAL_DEVICE *    pDevice
    )
    {
    USB2_SER_VDBG("usb2SerialWrsClose(): Start\n", 1, 2, 3, 4, 5, 6);

    if (pDevice == NULL)
        {
        USB2_SER_ERR("usb2SerialWrsClose(): Parameter error\n",
                     1, 2, 3, 4, 5, 6);

        return ERROR;
        }

    return OK;
    }

/*******************************************************************************
*
* usb2SerialWrsParseConfig - callback function for usb serial class driver
*
* This routine is called when usb serial class driver find a new device for this
* module.
*
* This routine check the device's interface and endpoint information. If check
* successfully, then return the device config information to usb serial class
* driver.
*
* RETURNS: USBHST_STATUS
*
* ERRNO: N/A
*
* \NOMANUAL
*/

LOCAL USBHST_STATUS usb2SerialWrsParseConfig
    (
    USB2_SERIAL_ADAPTER_INFO *      pDriverInfo,
    UINT32                          hDevice,
    UINT8                           bInterfaceNumber,
    USB2_SERIAL_DEVICE_CONFIG_INFO * pConfigInfo
    )
    {
    pUSBD_DEVICE_INFO               pDeviceInfo = NULL;

    pUSBHST_CONFIG_DESCRIPTOR       pCurrentConfiguration = NULL;
    pUSBD_INTERFACE_INFO            pInterface = NULL;
    USBHST_INTERFACE_DESCRIPTOR *   pInterfaceDesc = NULL;
    USBHST_ENDPOINT_DESCRIPTOR *    pEndPointDesc = NULL;
#if ((_WRS_VXWORKS_MAJOR == 6) && (_WRS_VXWORKS_MINOR >= 9)) || \
    (_WRS_VXWORKS_MAJOR > 6)
    USB_TRANSFER_SETUP_INFO         xferSetup;
#endif
    USBHST_STATUS                   hstStatus;

    /* get the device information, for the descriptors */

    usbdTranslateDeviceHandle(hDevice, &pDeviceInfo);

    if (NULL == pDeviceInfo)
        {
        USB2_SER_ERR("usb2SerialWrsParseConfig(): Device is not exist\n",
                     1, 2, 3, 4, 5, 6);

        return USBHST_FAILURE;
        }

    if (pDriverInfo != &usb2SerialWrsDriver)
        {
        USB2_SER_ERR("usb2SerialWrsParseConfig(): Unmatched driver\n",
                     1, 2, 3, 4, 4, 6);

        return USBHST_FAILURE;
        }

    /* get the configuration */

    pCurrentConfiguration = (pUSBHST_CONFIG_DESCRIPTOR)
                             pDeviceInfo->pCurrentConfiguration;

    if (NULL == pCurrentConfiguration)
        {
        USB2_SER_ERR("usb2SerialWrsParseConfig(): Configuration not readable\n",
                     1, 2, 3, 4, 5, 6);
        return USBHST_FAILURE;
        }

    /* bConfigurationValue is counting from 1 */

    if (pCurrentConfiguration->bConfigurationValue < 1)
        {
        USB2_SER_ERR("usb2SerialWrsParseConfig(): Configuration value 0x%X is "
                     "invalid\n", pCurrentConfiguration->bConfigurationValue,
                     2, 3, 4, 5, 6);

        return USBHST_FAILURE;
        }

    pConfigInfo->configurationIndex =
        (UINT8)(pCurrentConfiguration->bConfigurationValue - 1);

    /* set configuration */

    if (usbHstSetConfiguration(hDevice, pConfigInfo->configurationIndex) != OK)
        {
        USB2_SER_ERR("usb2SerialWrsParseConfig(): Failed to set configuration -"
                     " device not added.\n", 1, 2, 3, 4, 5, 6);

        return USBHST_FAILURE;
        }

    USB2_SER_DBG("usb2SerialWrsParseConfig(): Set device to Configuration index"
                 " 0x%X \n", pConfigInfo->configurationIndex, 2, 3, 4, 5, 6);

    /* this device must have to interface */

    if (pDeviceInfo->uInterfaceCount - 1 < bInterfaceNumber +
        USB2_SER_WRS_CDC_DATA_INTF)
        {
        USB2_SER_ERR("usb2SerialWrsParseConfig(): Interface count %d not "
                     "enough\n", pDeviceInfo->uInterfaceCount, 2, 3, 4, 5, 6);

        return USBHST_FAILURE;
        }

    /* find the control interface */

    pInterface = &(pDeviceInfo->pInterfacesInfo[bInterfaceNumber + USB2_SER_WRS_CDC_CTRL_INTF]);

    /* get the alternate setting */

    pConfigInfo->uAlternateSetting = pInterface->uAlternateSetting;

    /* set interface */

    if ((hstStatus = usbHstSetInterface(hDevice,
                        bInterfaceNumber + USB2_SER_WRS_CDC_CTRL_INTF,
                        pConfigInfo->uAlternateSetting)) != USBHST_SUCCESS)
        {
        USB2_SER_ERR("usb2SerialWrsParseConfig(): Failed to set interface -"
                     " device not added \n", 1, 2, 3, 4, 5, 6);

        if (hstStatus != USBHST_STALL_ERROR)
            return USBHST_FAILURE;
        }

    USB2_SER_DBG("usb2SerialWrsParseConfig(): Set control interface 0x%X \n",
                 bInterfaceNumber + USB2_SER_WRS_CDC_CTRL_INTF, 2, 3, 4, 5, 6);

    USB2_SER_DBG("usb2SerialWrsParseConfig(): Searching for endpoints... \n",
                 1, 2, 3, 4, 5, 6);

    /* locate the interface descriptor */

    pInterfaceDesc = usb2FindInterface(pDeviceInfo,
                                       bInterfaceNumber + USB2_SER_WRS_CDC_CTRL_INTF,
                                       pConfigInfo->uAlternateSetting);
    if (NULL == pInterfaceDesc)
        {
        USB2_SER_ERR("usb2SerialWrsParseConfig(): No such interface "
                     "bInterfaceNumber 0x%X, uAlternateSetting 0x%X on the "
                     "device\n", bInterfaceNumber + USB2_SER_WRS_CDC_CTRL_INTF,
                     pConfigInfo->uAlternateSetting,
                     3, 4, 5, 6);

        return USBHST_FAILURE;
        }

    if (pInterfaceDesc->bInterfaceClass != USB2_SER_WRS_CTRL_INTF_CLASS ||
        pInterfaceDesc->bInterfaceSubClass != USB2_SER_WRS_CTRL_INTF_SUB_CLASS)
        {
        USB2_SER_ERR("usb2SerialWrsParseConfig(): Control interface class not "
                     "match REQ[0x%02X, 0x%02X] GET[0x%02X, 0x%02X]\n",
                     USB2_SER_WRS_CTRL_INTF_CLASS,
                     USB2_SER_WRS_CTRL_INTF_SUB_CLASS,
                     pInterfaceDesc->bInterfaceClass,
                     pInterfaceDesc->bInterfaceSubClass, 5, 6);

        return USBHST_FAILURE;
        }

    /* find the interrupt control endpoint */

    pEndPointDesc = usb2FindEndpoint((UCHAR* )pInterfaceDesc,
                                     USB2_ATTR_INTERRUPT,
                                     USB2_ENDPOINT_IN,
                                     0, 0, 0);
    if (NULL == pEndPointDesc)
        {
        USB2_SER_ERR("usb2SerialWrsParseConfig(): No INT IN endpoint found, "
                     "Resetting Error \n", 1, 2, 3, 4, 5, 6);

        return USBHST_FAILURE;
        }

    pConfigInfo->uSetupEndPointAddr = pEndPointDesc->bEndpointAddress;

    USB2_SER_DBG("usb2SerialWrsParseConfig(): Found endpoints INT[0x%X]\n",
                 pConfigInfo->uSetupEndPointAddr, 2, 3, 4, 5, 6);

    pConfigInfo->wMaxPacketSizeSetup =
        OS_UINT16_LE_TO_CPU(pEndPointDesc->wMaxPacketSize);

#if ((_WRS_VXWORKS_MAJOR == 6) && (_WRS_VXWORKS_MINOR >= 9)) || \
    (_WRS_VXWORKS_MAJOR > 6)

    /* set up the trasnfer requirment */

    OS_MEMSET(&xferSetup, 0, sizeof(USB_TRANSFER_SETUP_INFO));

    xferSetup.uMaxTransferSize = max(_CACHE_ALIGN_SIZE,
                                     pConfigInfo->wMaxPacketSizeOut);

    xferSetup.uMaxNumReqests = 1;
    xferSetup.uFlags = 0;

    hstStatus = usbHstPipePrepare(hDevice,
                                  pEndPointDesc->bEndpointAddress,
                                  &xferSetup);

    if (hstStatus != USBHST_SUCCESS)
        {
        USB2_SER_ERR("usb2SerialWrsParseConfig(): bulk OUT at 0x%x set up "
                     "failed\n", pEndPointDesc->bEndpointAddress, 2, 3, 4, 5, 6);

        return USBHST_FAILURE;
        }
#endif

    /* find the data interface */

    pInterface = &(pDeviceInfo->pInterfacesInfo[bInterfaceNumber + USB2_SER_WRS_CDC_DATA_INTF]);

    /* set interface */

    if ((hstStatus = usbHstSetInterface(hDevice,
                        (UINT16) ((bInterfaceNumber + USB2_SER_WRS_CDC_DATA_INTF) & 0xFF),
                        pInterface->uAlternateSetting)) != USBHST_SUCCESS)
        {
        USB2_SER_ERR("usb2SerialWrsParseConfig(): Failed to set interface -"
                     " device not added \n", 1, 2, 3, 4, 5, 6);

        if (hstStatus != USBHST_STALL_ERROR)
            return USBHST_FAILURE;
        }

    USB2_SER_DBG("usb2SerialWrsParseConfig(): Set data interface 0x%X \n",
                 bInterfaceNumber + USB2_SER_WRS_CDC_DATA_INTF, 2, 3, 4, 5, 6);

    USB2_SER_DBG("usb2SerialWrsParseConfig(): Searching for endpoints... \n",
                 1, 2, 3, 4, 5, 6);


    /* locate the interface descriptor */

    pInterfaceDesc = usb2FindInterface(pDeviceInfo,
                         (UINT16) ((bInterfaceNumber + USB2_SER_WRS_CDC_DATA_INTF) & 0xFF),
                         pInterface->uAlternateSetting);

    if (NULL == pInterfaceDesc)
        {
        USB2_SER_ERR("usb2SerialWrsParseConfig(): No such interface "
                     "bInterfaceNumber 0x%X, uAlternateSetting 0x%X on the "
                     "device\n", bInterfaceNumber + USB2_SER_WRS_CDC_DATA_INTF,
                     pInterface->uAlternateSetting,
                     3, 4, 5, 6);

        return USBHST_FAILURE;
        }

    if (pInterfaceDesc->bInterfaceClass != USB2_SER_WRS_DATA_INTF_CLASS ||
        pInterfaceDesc->bInterfaceSubClass != USB2_SER_WRS_DATA_INTF_SUB_CLASS)
        {
        USB2_SER_ERR("usb2SerialWrsParseConfig(): Data interface class not match "
                     "REQ[0x%02X, 0x%02X] GET[0x%02X, 0x%02X]\n",
                     USB2_SER_WRS_DATA_INTF_CLASS,
                     USB2_SER_WRS_DATA_INTF_SUB_CLASS,
                     pInterfaceDesc->bInterfaceClass,
                     pInterfaceDesc->bInterfaceSubClass, 5, 6);

        return USBHST_FAILURE;
        }

    /* find the first BULK IN endpoint */

    pEndPointDesc = usb2FindEndpoint((UCHAR* )pInterfaceDesc,
                                     USB2_ATTR_BULK,
                                     USB2_ENDPOINT_IN,
                                     0, 0, 0);

    if (NULL == pEndPointDesc)
        {
        USB2_SER_ERR("usb2SerialWrsAdd(): No bulk IN endpoint found, "
                     "Resetting Error \n", 1, 2, 3, 4, 5, 6);

        return USBHST_FAILURE;
        }

    pConfigInfo->uInEndPointAddr = pEndPointDesc->bEndpointAddress;

    pConfigInfo->wMaxPacketSizeIn = OS_UINT16_LE_TO_CPU(pEndPointDesc->wMaxPacketSize);

#if ((_WRS_VXWORKS_MAJOR == 6) && (_WRS_VXWORKS_MINOR >= 9)) || \
    (_WRS_VXWORKS_MAJOR > 6)

    /* set up the trasnfer requirment */

    OS_MEMSET(&xferSetup, 0, sizeof(USB_TRANSFER_SETUP_INFO));

    /*
     * if the adapter driver did not give a max packet size value, use endpoint
     * max packet size.
     */

    if (pDriverInfo->uMaxInPacketSize != 0)
        xferSetup.uMaxTransferSize = max(_CACHE_ALIGN_SIZE,
                                         pDriverInfo->uMaxInPacketSize);
    else
        xferSetup.uMaxTransferSize = max(_CACHE_ALIGN_SIZE,
                                         pConfigInfo->wMaxPacketSizeIn);

    xferSetup.uMaxNumReqests = 1;
    xferSetup.uFlags = 0;

    hstStatus = usbHstPipePrepare(hDevice,
                                  pEndPointDesc->bEndpointAddress,
                                  &xferSetup);

    if (hstStatus != USBHST_SUCCESS)
        {
        USB2_SER_ERR("usb2SerialWrsParseConfig(): Bulk IN at 0x%x set up "
                     "failed\n", pEndPointDesc->bEndpointAddress, 2, 3, 4, 5, 6);

        return USBHST_FAILURE;
        }
#endif

    /* find for the first BULK OUT endpoint */

    pEndPointDesc = usb2FindEndpoint((UCHAR* )pInterfaceDesc,
                                     USB2_ATTR_BULK,
                                     USB2_ENDPOINT_OUT,
                                     0, 0, 0);

    if (NULL == pEndPointDesc)
        {
        USB2_SER_ERR("usb2SerialWrsParseConfig(): No bulk OUT Endpoint found, "
                     "returning error\n", 1, 2, 3, 4, 5, 6);

        return USBHST_FAILURE;
        }

    pConfigInfo->uOutEndPointAddr = pEndPointDesc->bEndpointAddress;

    USB2_SER_DBG("usb2SerialWrsParseConfig(): Found endpoints "
                 "IN[0x%X], OUT[0x%X]\n", pConfigInfo->uInEndPointAddr,
                 pConfigInfo->uOutEndPointAddr,
                 3, 4, 5, 6);

    pConfigInfo->wMaxPacketSizeOut =
        OS_UINT16_LE_TO_CPU(pEndPointDesc->wMaxPacketSize);

#if ((_WRS_VXWORKS_MAJOR == 6) && (_WRS_VXWORKS_MINOR >= 9)) || \
    (_WRS_VXWORKS_MAJOR > 6)

    /* set up the trasnfer requirment */

    OS_MEMSET(&xferSetup, 0, sizeof(USB_TRANSFER_SETUP_INFO));

    /*
     * if the adapter driver did not give a max packet size value, use endpoint
     * max packet size.
     */

    if (pDriverInfo->uMaxOutPacketSize != 0)
        xferSetup.uMaxTransferSize = max(_CACHE_ALIGN_SIZE,
                                         pDriverInfo->uMaxOutPacketSize);
    else
        xferSetup.uMaxTransferSize = max(_CACHE_ALIGN_SIZE,
                                         pConfigInfo->wMaxPacketSizeOut);

    xferSetup.uMaxNumReqests = 1;
    xferSetup.uFlags = 0;

    hstStatus = usbHstPipePrepare(hDevice,
                                  pEndPointDesc->bEndpointAddress,
                                  &xferSetup);

    if (hstStatus != USBHST_SUCCESS)
        {
        USB2_SER_ERR("usb2SerialWrsParseConfig(): bulk OUT at 0x%x set up "
                     "failed\n", pEndPointDesc->bEndpointAddress, 2, 3, 4, 5, 6);

        return USBHST_FAILURE;
        }
#endif

    return USBHST_SUCCESS;
    }

/*******************************************************************************
*
* usb2SerialWrsInit - register WRS USB CDC Serial emulator adapter driver
*
* This routine is called once to register WRS USB CDC Serial emulator adapter
* driver. The driver install IOS function to USB-to-Serial class driver.
* It must be run BEFORE class driver initialization.
*
* RETURNS: OK or ERROR if the driver could not be initialized
*
* ERRNO: N/A
*/

STATUS usb2SerialWrsInit
    (
    INT8 *  pName /* Default base name that will be used, or NULL if no names */
    )
    {
    USB2_SER_DBG("usb2SerialWrsInit(): Register Wind River Systems USB CDC "
                 "Serial emulator driver\n", 1, 2, 3, 4, 5, 6);

    if (pName != NULL)
        usb2SerialWrsDriver.deviceName = pName;

    return usb2SerialDeviceIdAdd(usb2SerialWrsDevIdList,
                                 &usb2SerialWrsDriver,
                                 WRS_CDC_DEVICE_ID_COUNT);

    }

